From: Magdalen Berns Date: Wed, 4 Sep 2013 17:53:36 +0000 (+0100) Subject: Magnifier: Implement focus and caret tracking X-Git-Tag: archive/raspbian/50.5-1+rpi1~1^2~1013^2^2~3500 X-Git-Url: https://dgit.raspbian.org/%22http://www.example.com/cgi/%22/%22http:/www.example.com/cgi/%22?a=commitdiff_plain;h=9d8f30f955fcd15a509a19310c0e946f13eca3a6;p=gnome-shell.git Magnifier: Implement focus and caret tracking A11y users who use the magnifier may have trouble focusing when they're typing or trying to keynav. Implement a new system so that they can have the magnifier track the caret and focus instead instead of just the mouse. Bug https://bugzilla.gnome.org/show_bug.cgi?id=647074 --- diff --git a/js/Makefile.am b/js/Makefile.am index f9a1474c4..7df786f2a 100644 --- a/js/Makefile.am +++ b/js/Makefile.am @@ -55,6 +55,7 @@ nobase_dist_js_DATA = \ ui/extensionSystem.js \ ui/extensionDownloader.js \ ui/environment.js \ + ui/focusCaretTracker.js\ ui/ibusCandidatePopup.js\ ui/grabHelper.js \ ui/iconGrid.js \ diff --git a/js/ui/focusCaretTracker.js b/js/ui/focusCaretTracker.js new file mode 100644 index 000000000..c4c4d7f63 --- /dev/null +++ b/js/ui/focusCaretTracker.js @@ -0,0 +1,68 @@ +/** -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + * Copyright 2012 Inclusive Design Research Centre, OCAD University. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Author: + * Joseph Scheuhammer + * Contributor: + * Magdalen Berns + */ + +const Atspi = imports.gi.Atspi; +const Lang = imports.lang; +const Signals = imports.signals; + +const CARETMOVED = 'object:text-caret-moved'; +const STATECHANGED = 'object:state-changed'; + +const FocusCaretTracker = new Lang.Class({ + Name: 'FocusCaretTracker', + + _init: function() { + Atspi.init(); + this._atspiListener = Atspi.EventListener.new(Lang.bind(this, this._onChanged)); + }, + + _onChanged: function(event) { + let update = null; + + if (event.type.indexOf(STATECHANGED) == 0) + update = 'focus-changed'; + else if (event.type == CARETMOVED) + update = 'caret-moved'; + if(update != null) + this.emit(update, event); + }, + + registerFocusListener: function() { + return this._atspiListener.register(STATECHANGED + ':focused') && + this._atspiListener.register(STATECHANGED + ':selected'); + }, + + registerCaretListener: function() { + return this._atspiListener.register(CARETMOVED); + }, + + deregisterFocusListener: function() { + return this._atspiListener.deregister(STATECHANGED + ':focused') && + this._atspiListener.deregister(STATECHANGED + ':selected'); + }, + + deregisterCaretListener: function() { + return this._atspiListener.deregister(CARETMOVED); + } +}); +Signals.addSignalMethods(FocusCaretTracker.prototype); diff --git a/js/ui/magnifier.js b/js/ui/magnifier.js index 7c68ea8ec..6f7fc56b6 100644 --- a/js/ui/magnifier.js +++ b/js/ui/magnifier.js @@ -1,5 +1,6 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- +const Atspi = imports.gi.Atspi; const Clutter = imports.gi.Clutter; const GDesktopEnums = imports.gi.GDesktopEnums; const Gio = imports.gi.Gio; @@ -10,6 +11,7 @@ const Mainloop = imports.mainloop; const Meta = imports.gi.Meta; const Signals = imports.signals; +const FocusCaretTracker = imports.ui.focusCaretTracker; const Main = imports.ui.main; const MagnifierDBus = imports.ui.magnifierDBus; const Params = imports.misc.params; @@ -37,6 +39,8 @@ const CONTRAST_BLUE_KEY = 'contrast-blue'; const LENS_MODE_KEY = 'lens-mode'; const CLAMP_MODE_KEY = 'scroll-at-edges'; const MOUSE_TRACKING_KEY = 'mouse-tracking'; +const FOCUS_TRACKING_KEY = 'focus-tracking'; +const CARET_TRACKING_KEY = 'caret-tracking'; const SHOW_CROSS_HAIRS_KEY = 'show-cross-hairs'; const CROSS_HAIRS_THICKNESS_KEY = 'cross-hairs-thickness'; const CROSS_HAIRS_COLOR_KEY = 'cross-hairs-color'; @@ -449,6 +453,14 @@ const Magnifier = new Lang.Class({ if (aPref) zoomRegion.setMouseTrackingMode(aPref); + aPref = this._settings.get_enum(FOCUS_TRACKING_KEY); + if (aPref) + zoomRegion.setFocusTrackingMode(aPref); + + aPref = this._settings.get_enum(CARET_TRACKING_KEY); + if (aPref) + zoomRegion.setCaretTrackingMode(aPref); + aPref = this._settings.get_boolean(INVERT_LIGHTNESS_KEY); if (aPref) zoomRegion.setInvertLightness(aPref); @@ -488,6 +500,10 @@ const Magnifier = new Lang.Class({ Lang.bind(this, this._updateClampMode)); this._settings.connect('changed::' + MOUSE_TRACKING_KEY, Lang.bind(this, this._updateMouseTrackingMode)); + this._settings.connect('changed::' + FOCUS_TRACKING_KEY, + Lang.bind(this, this._updateFocusTrackingMode)); + this._settings.connect('changed::' + CARET_TRACKING_KEY, + Lang.bind(this, this._updateCaretTrackingMode)); this._settings.connect('changed::' + INVERT_LIGHTNESS_KEY, Lang.bind(this, this._updateInvertLightness)); @@ -585,6 +601,24 @@ const Magnifier = new Lang.Class({ } }, + _updateFocusTrackingMode: function() { + // Applies only to the first zoom region. + if (this._zoomRegions.length) { + this._zoomRegions[0].setFocusTrackingMode( + this._settings.get_enum(FOCUS_TRACKING_KEY) + ); + } + }, + + _updateCaretTrackingMode: function() { + // Applies only to the first zoom region. + if (this._zoomRegions.length) { + this._zoomRegions[0].setCaretTrackingMode( + this._settings.get_enum(CARET_TRACKING_KEY) + ); + } + }, + _updateInvertLightness: function() { // Applies only to the first zoom region. if (this._zoomRegions.length) { @@ -623,7 +657,7 @@ const Magnifier = new Lang.Class({ contrast.b = this._settings.get_double(CONTRAST_BLUE_KEY); this._zoomRegions[0].setContrast(contrast); } - }, + } }); Signals.addSignalMethods(Magnifier.prototype); @@ -632,8 +666,11 @@ const ZoomRegion = new Lang.Class({ _init: function(magnifier, mouseSourceActor) { this._magnifier = magnifier; + this._focusCaretTracker = new FocusCaretTracker.FocusCaretTracker(); this._mouseTrackingMode = GDesktopEnums.MagnifierMouseTrackingMode.NONE; + this._focusTrackingMode = GDesktopEnums.MagnifierFocusTrackingMode.NONE; + this._caretTrackingMode = GDesktopEnums.MagnifierCaretTrackingMode.NONE; this._clampScrollingAtEdges = false; this._lensMode = false; this._screenPosition = GDesktopEnums.MagnifierScreenPosition.FULL_SCREEN; @@ -659,9 +696,35 @@ const ZoomRegion = new Lang.Class({ this._xMagFactor = 1; this._yMagFactor = 1; this._followingCursor = false; + this._xFocus = 0; + this._yFocus = 0; + this._xCaret = 0; + this._yCaret = 0; Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._monitorsChanged)); + this._focusCaretTracker.connect('caret-moved', + Lang.bind(this, this._updateCaret)); + this._focusCaretTracker.connect('focus-changed', + Lang.bind(this, this._updateFocus)); + }, + + _updateFocus: function(caller, event) { + let component = event.source.get_component_iface(); + if (!component || event.detail1 != 1) + return; + let extents = component.get_extents(Atspi.CoordType.SCREEN); + [this._xFocus, this._yFocus] = [extents.x, extents.y] + this._centerFromFocusPosition(); + }, + + _updateCaret: function(caller, event) { + let text = event.source.get_text_iface(); + if (!text) + return; + let extents = text.get_character_extents(text.get_caret_offset(), 0); + [this._xCaret, this._yCaret] = [extents.x, extents.y]; + this._centerFromCaretPosition(); }, /** @@ -732,6 +795,30 @@ const ZoomRegion = new Lang.Class({ return this._mouseTrackingMode; }, + /** + * setFocusTrackingMode + * @mode: One of the enum FocusTrackingMode values. + */ + setFocusTrackingMode: function(mode) { + this._focusTrackingMode = mode; + if (this._focusTrackingMode == GDesktopEnums.MagnifierFocusTrackingMode.NONE) + this._focusCaretTracker.deregisterFocusListener(); + else + this._focusCaretTracker.registerFocusListener(); + }, + + /** + * setCaretTrackingMode + * @mode: One of the enum CaretTrackingMode values. + */ + setCaretTrackingMode: function(mode) { + this._caretTrackingMode = mode; + if (this._caretTrackingMode == GDesktopEnums.MagnifierCaretTrackingMode.NONE) + this._focusCaretTracker.deregisterCaretListener(); + else + this._focusCaretTracker.registerCaretListener(); + }, + /** * setViewPort * Sets the position and size of the ZoomRegion on screen. @@ -1243,19 +1330,47 @@ const ZoomRegion = new Lang.Class({ let yMouse = this._magnifier.yMouse; if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.PROPORTIONAL) { - return this._centerFromMouseProportional(xMouse, yMouse); + return this._centerFromPointProportional(xMouse, yMouse); } else if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.PUSH) { - return this._centerFromMousePush(xMouse, yMouse); + return this._centerFromPointPush(xMouse, yMouse); } else if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.CENTERED) { - return this._centerFromMouseCentered(xMouse, yMouse); + return this._centerFromPointCentered(xMouse, yMouse); } return null; // Should never be hit }, - _centerFromMousePush: function(xMouse, yMouse) { + _centerFromCaretPosition: function() { + let xCaret = this._xCaret; + let yCaret = this._yCaret; + + if (this._caretTrackingMode == GDesktopEnums.MagnifierCaretTrackingMode.PROPORTIONAL) + [xCaret, yCaret] = this._centerFromPointProportional(xCaret, yCaret); + else if (this._caretTrackingMode == GDesktopEnums.MagnifierCaretTrackingMode.PUSH) + [xCaret, yCaret] = this._centerFromPointPush(xCaret, yCaret); + else if (this._caretTrackingMode == GDesktopEnums.MagnifierCaretTrackingMode.CENTERED) + [xCaret, yCaret] = this._centerFromPointCentered(xCaret, yCaret); + + this.scrollContentsTo(xCaret, yCaret); + }, + + _centerFromFocusPosition: function() { + let xFocus = this._xFocus; + let yFocus = this._yFocus; + + if (this._focusTrackingMode == GDesktopEnums.MagnifierFocusTrackingMode.PROPORTIONAL) + [xFocus, yFocus] = this._centerFromPointProportional(xFocus, yFocus); + else if (this._focusTrackingMode == GDesktopEnums.MagnifierFocusTrackingMode.PUSH) + [xFocus, yFocus] = this._centerFromPointPush(xFocus, yFocus); + else if (this._focusTrackingMode == GDesktopEnums.MagnifierFocusTrackingMode.CENTERED) + [xFocus, yFocus] = this._centerFromPointCentered(xFocus, yFocus); + + this.scrollContentsTo(xFocus, yFocus); + }, + + _centerFromPointPush: function(xPoint, yPoint) { let [xRoi, yRoi, widthRoi, heightRoi] = this.getROI(); let [cursorWidth, cursorHeight] = this._mouseSourceActor.get_size(); let xPos = xRoi + widthRoi / 2; @@ -1263,20 +1378,20 @@ const ZoomRegion = new Lang.Class({ let xRoiRight = xRoi + widthRoi - cursorWidth; let yRoiBottom = yRoi + heightRoi - cursorHeight; - if (xMouse < xRoi) - xPos -= (xRoi - xMouse); - else if (xMouse > xRoiRight) - xPos += (xMouse - xRoiRight); + if (xPoint < xRoi) + xPos -= (xRoi - xPoint); + else if (xPoint > xRoiRight) + xPos += (xPoint - xRoiRight); - if (yMouse < yRoi) - yPos -= (yRoi - yMouse); - else if (yMouse > yRoiBottom) - yPos += (yMouse - yRoiBottom); + if (yPoint < yRoi) + yPos -= (yRoi - yPoint); + else if (yPoint > yRoiBottom) + yPos += (yPoint - yRoiBottom); return [xPos, yPos]; }, - _centerFromMouseProportional: function(xMouse, yMouse) { + _centerFromPointProportional: function(xPoint, yPoint) { let [xRoi, yRoi, widthRoi, heightRoi] = this.getROI(); let halfScreenWidth = global.screen_width / 2; let halfScreenHeight = global.screen_height / 2; @@ -1285,16 +1400,16 @@ const ZoomRegion = new Lang.Class({ let unscaledPadding = Math.min(this._viewPortWidth, this._viewPortHeight) / 5; let xPadding = unscaledPadding / this._xMagFactor; let yPadding = unscaledPadding / this._yMagFactor; - let xProportion = (xMouse - halfScreenWidth) / halfScreenWidth; // -1 ... 1 - let yProportion = (yMouse - halfScreenHeight) / halfScreenHeight; // -1 ... 1 - let xPos = xMouse - xProportion * (widthRoi / 2 - xPadding); - let yPos = yMouse - yProportion * (heightRoi /2 - yPadding); + let xProportion = (xPoint - halfScreenWidth) / halfScreenWidth; // -1 ... 1 + let yProportion = (yPoint - halfScreenHeight) / halfScreenHeight; // -1 ... 1 + let xPos = xPoint - xProportion * (widthRoi / 2 - xPadding); + let yPos = yPoint - yProportion * (heightRoi /2 - yPadding); return [xPos, yPos]; }, - _centerFromMouseCentered: function(xMouse, yMouse) { - return [xMouse, yMouse]; + _centerFromPointCentered: function(xPoint, yPoint) { + return [xPoint, yPoint]; }, _screenToViewPort: function(screenX, screenY) {